package apk

// APK is the archival format used for Android apps. It is a ZIP archive with
// three extra files:
//
//	META-INF/MANIFEST.MF
//	META-INF/CERT.SF
//	META-INF/CERT.RSA
//
// The MANIFEST.MF comes from the Java JAR archive format. It is a list of
// files included in the archive along with a SHA1 hash, for example:
//
//	Name: lib/armeabi/libbasic.so
//	SHA1-Digest: ntLSc1eLCS2Tq1oB4Vw6jvkranw=
//
// For debugging, the equivalent SHA1-Digest can be generated with OpenSSL:
//
//	cat lib/armeabi/libbasic.so | openssl sha1 -binary | openssl base64
//
// CERT.SF is a similar manifest. It begins with a SHA1 digest of the entire
// manifest file:
//
//	Signature-Version: 1.0
//	Created-By: 1.0 (Android)
//	SHA1-Digest-Manifest: aJw+u+10C3Enbg8XRCN6jepluYA=
//
// Then for each entry in the manifest it has a SHA1 digest of the manfiest's
// hash combined with the file name:
//
//	Name: lib/armeabi/libbasic.so
//	SHA1-Digest: Q7NAS6uzrJr6WjePXSGT+vvmdiw=
//
// This can also be generated with openssl:
//
//	echo -en "Name: lib/armeabi/libbasic.so\r\nSHA1-Digest: ntLSc1eLCS2Tq1oB4Vw6jvkranw=\r\n\r\n" | openssl sha1 -binary | openssl base64
//
// Note the \r\n line breaks.
//
// CERT.RSA is an RSA signature block made of CERT.SF. Verify it with:
//
//	openssl smime -verify -in CERT.RSA -inform DER -content CERT.SF cert.pem
//
// The APK format imposes two extra restrictions on the ZIP format. First,
// it is uncompressed. Second, each contained file is 4-byte aligned. This
// allows the Android OS to mmap contents without unpacking the archive.
//
// todo: lib so file need align to 4K bytes.
// ref : https://developer.android.com/studio/command-line/zipalign
//
// Note: to make life a little harder, Android Studio stores the RSA key used
// for signing in an Oracle Java proprietary keystore format, JKS. For example,
// the generated debug key is in ~/.android/debug.keystore, and can be
// extracted using the JDK's keytool utility:
//
//	keytool -importkeystore -srckeystore ~/.android/debug.keystore -destkeystore ~/.android/debug.p12 -deststoretype PKCS12
//
// Once in standard PKCS12, the key can be converted to PEM for use in the
// Go crypto packages:
//
//	openssl pkcs12 -in ~/.android/debug.p12 -nocerts -nodes -out ~/.android/debug.pem
//
// Fortunately for debug builds, all that matters is that the APK is signed.
// The choice of key is unimportant, so we can generate one for normal builds.
// For production builds, we can ask users to provide a PEM file.

import (
	"archive/zip"
	"crypto/rsa"
	"crypto/sha1"
	"errors"
	"fmt"
	"hash"
	"io"
)

type manifestEntry struct {
	name string
	sha1 hash.Hash
}

type countWriter struct {
	w     io.Writer
	count int64
}

func (w *countWriter) Write(p []byte) (int, error) {
	n, err := w.w.Write(p)
	w.count += int64(n)
	return n, err
}

type Zapk struct {
	zw *zip.Writer
	fp *countWriter

	/**/
	priv     *rsa.PrivateKey
	manifest []manifestEntry
}

func NewWriter(fws io.WriteSeeker, priv *rsa.PrivateKey) *Zapk {
	fp := &countWriter{fws, 0}
	zk := &Zapk{fp: fp, priv: priv}
	zk.zw = zip.NewWriter(fp)
	return zk
}

func (zk *Zapk) create(fname string) (io.Writer, error) {
	const fileHeaderLen = 30 // + filename + extra

	/**/
	zk.zw.Flush()
	ofs := zk.fp.count + fileHeaderLen + int64(len(fname))
	tsiz := int(-ofs) & 0x3
	fmt.Printf("align, ofs = %x, pad = %v\n", ofs, tsiz)

	hdr := &zip.FileHeader{Name: fname, Method: zip.Store, Extra: make([]byte, tsiz)}
	dst, err := zk.zw.CreateHeader(hdr)
	if err != nil {
		return nil, err
	}

	return dst, nil
}

func (zk *Zapk) AddFromFile(fname string, src io.Reader) error {
	var hs hash.Hash

	/**/
	dst, err := zk.create(fname)
	if err != nil {
		return err
	}

	/**/
	buf := make([]byte, 32768)
	hs = sha1.New()

	for {
		nr, er := src.Read(buf)
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw < 0 || nr < nw {
				nw = 0
				if ew == nil {
					return errors.New("invalid io write.")
				}
			}

			if ew != nil {
				return ew
			}

			if nr != nw {
				return io.ErrShortWrite
			}

			/**/
			hs.Write(buf[0:nr])
		}

		if er != nil {
			if er == io.EOF {
				break
			} else {
				return er
			}
		}
	}

	/**/
	zk.manifest = append(zk.manifest, manifestEntry{
		name: fname,
		sha1: hs,
	})

	return nil
}

func (zk *Zapk) AddFromBytes(fname string, dat []byte) error {
	var hs hash.Hash

	/**/
	dst, err := zk.create(fname)
	if err != nil {
		return err
	}

	/**/
	hs = sha1.New()

	/**/
	_, err = dst.Write(dat)
	if err != nil {
		return err
	}

	/**/
	hs.Write(dat)
	zk.manifest = append(zk.manifest, manifestEntry{
		name: fname,
		sha1: hs,
	})

	return nil
}

const manifestHeader = `Manifest-Version: 1.0
Created-By: 1.0 (Go)

`

const manifestDexHeader = `Manifest-Version: 1.0
Created-By: 1.0 (Go)
Dex-Location: classes.dex

`

const certHeader = `Signature-Version: 1.0
Created-By: 1.0 (Go)
`

/*
manifest : META-INF/MANIFEST.MF
    cert : META-INF/CERT.SF
	 rsa : META-INF/CERT.RSA
*/
func (zk *Zapk) Close() error {
	/*
		hasDex := false
		for _, entry := range zk.manifest {
			if entry.name == "classes.dex" {
				hasDex = true
				break
			}
		}

		manifest := new(bytes.Buffer)
		if hasDex {
			fmt.Fprint(manifest, manifestDexHeader)
		} else {
			fmt.Fprint(manifest, manifestHeader)
		}

		// certBody := new(bytes.Buffer)
		for _, entry := range zk.manifest {
			n := entry.name
			h := base64.StdEncoding.EncodeToString(entry.sha1.Sum(nil))
			fmt.Fprintf(manifest, "Name: %s\nSHA1-Digest: %s\n\n", n, h)


				cHash := sha1.New()
				fmt.Fprintf(cHash, "Name: %s\r\nSHA1-Digest: %s\r\n\r\n", n, h)
				ch := base64.StdEncoding.EncodeToString(cHash.Sum(nil))
				fmt.Fprintf(certBody, "Name: %s\nSHA1-Digest: %s\n\n", n, ch)

		}


			mHash := sha1.New()
			mHash.Write(manifest.Bytes())
			cert := new(bytes.Buffer)
			fmt.Fprint(cert, certHeader)
			fmt.Fprintf(cert, "SHA1-Digest-Manifest: %s\n\n", base64.StdEncoding.EncodeToString(mHash.Sum(nil)))
			cert.Write(certBody.Bytes())
	*/

	// NANIFEST.MF
	/*
		mw, err := zk.create("META-INF/MANIFEST.MF")
		if err != nil {
			return err
		}
		if _, err := mw.Write(manifest.Bytes()); err != nil {
			return err
		}


			// CERT.SF
			cw, err := zk.create("META-INF/CERT.SF")
			if err != nil {
				return err
			}
			if _, err := cw.Write(cert.Bytes()); err != nil {
				return err
			}

			// CERT.RSA
			rsa, err := signPKCS7(rand.Reader, zk.priv, cert.Bytes())
			if err != nil {
				return fmt.Errorf("apk: %v", err)
			}
			rw, err := zk.create("META-INF/CERT.RSA")
			if err != nil {
				return err
			}
			if _, err := rw.Write(rsa); err != nil {
				return err
			}

	*/

	/**/
	return zk.zw.Close()
}
